﻿using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace LeanEngine.Extensions
{
    /// <summary>
    /// Extension methods for the data type System.Object.
    /// </summary>
    public static class ObjectExtensions
    {
        #region Declarations

        private const string NumericRegexPattern = @"^[0-9]+([,\.][0-9]+)?$";

        #endregion

        #region Parse Extensions

        #region Invariant Culture

        /// <summary>
        /// Change the object type for the specified type
        /// </summary>
        /// <typeparam name="T">The type to change for</typeparam>
        public static T ChangeType<T>(this Object source)
        {
            object convertedValue = Convert.ChangeType(source, typeof(T), CultureInfo.CurrentCulture);
            return (T)convertedValue;
        }

        #endregion

        #region Default Culture

        /// <summary>
        /// Convert to an Int16 type using the current culture-specific formating information
        /// </summary>
        public static Int16 ToInt16(this object value)
        {
            if (value == null) return Int16.MinValue;

            var parsedValue = Int16.MinValue;
            Int16.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.CurrentUICulture.NumberFormat, out parsedValue);
            
            return parsedValue;
        }

        /// <summary>
        /// Convert to an Int13 type using the current culture-specific formating information
        /// </summary>
        public static Int32 ToInt32(this object value)
        {
            if (value == null) return Int32.MinValue;

            var parsedValue = Int32.MinValue;
            Int32.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.CurrentUICulture.NumberFormat, out parsedValue);
            
            return parsedValue;
        }

        /// <summary>
        /// Convert to an Int64 type using the current culture-specific formating information
        /// </summary>
        public static Int64 ToInt64(this object value)
        {
            if (value == null) return Int64.MinValue;

            var parsedValue = Int64.MinValue;
            Int64.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.CurrentUICulture.NumberFormat, out parsedValue);
            
            return parsedValue;
        }

        /// <summary>
        /// Convert to a DateTime type using the current culture-specific formating information
        /// </summary>
        public static DateTime ToDateTime(this object value)
        {
            if (value == null) return DateTime.MinValue;

            var parsedValue = DateTime.MinValue;
            DateTime.TryParse(value.ToString(), out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to a Double type using the current culture-specific formating information
        /// </summary>
        public static double ToDouble(this object value)
        {
            if (value == null) return double.MinValue;

            var parsedValue = double.MinValue;
            double.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.CurrentUICulture.NumberFormat, out parsedValue);
            
            return parsedValue;
        }

        /// <summary>
        /// Convert to a Decimal type using the current culture-specific formating information
        /// </summary>
        public static decimal ToDecimal(this object value)
        {
            if (value == null) return decimal.MinValue;

            var parsedValue = decimal.MinValue;
            decimal.TryParse(value.ToString(), NumberStyles.AllowDecimalPoint, CultureInfo.CurrentUICulture.NumberFormat, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to a Byte type using the current culture-specific formating information
        /// </summary>
        public static byte ToByte(this object value)
        {
            if (value == null) return byte.MinValue;

            var parsedValue = byte.MinValue;
            byte.TryParse(value.ToString(), NumberStyles.Number, CultureInfo.CurrentUICulture.NumberFormat, out parsedValue);

            return parsedValue;
        }

        #endregion

        #region Custom Culture

        /// <summary>
        /// Convert to an Int16 type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static Int16 ToInt16(this object value, IFormatProvider culture)
        {
            if (value == null) return Int16.MinValue;

            var parsedValue = Int16.MinValue;
            Int16.TryParse(value.ToString(), NumberStyles.Integer, culture, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to an Int32 type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static Int32 ToInt32(this object value, IFormatProvider culture)
        {
            if (value == null) return Int32.MinValue;

            var parsedValue = Int32.MinValue;
            Int32.TryParse(value.ToString(), NumberStyles.Integer, culture, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to an Int64 type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static Int64 ToInt64(this object value, IFormatProvider culture)
        {
            if (value == null) return Int64.MinValue;

            var parsedValue = Int64.MinValue;
            Int64.TryParse(value.ToString(), NumberStyles.Integer, culture, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to a DateTime type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static DateTime ToDateTime(this object value, IFormatProvider culture)
        {
            if (value == null) return DateTime.MinValue;

            var parsedValue = DateTime.MinValue;
            DateTime.TryParse(value.ToString(), culture, DateTimeStyles.None, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to a Double type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static double ToDouble(this object value, CultureInfo culture)
        {
            if (value == null) return double.MinValue;

            var parsedValue = double.MinValue;
            double.TryParse(value.ToString(), NumberStyles.Float, culture, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to a Decimal type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static decimal ToDecimal(this object value, CultureInfo culture)
        {
            if (value == null) return decimal.MinValue;

            var parsedValue = decimal.MinValue;
            decimal.TryParse(value.ToString(), NumberStyles.AllowDecimalPoint, culture, out parsedValue);

            return parsedValue;
        }

        /// <summary>
        /// Convert to a Byte type using the specified culture-specific formatting information
        /// </summary>
        /// <param name="value">The object extended</param>
        /// <param name="culture">The culture-specific formatting information</param>
        public static byte ToByte(this object value, IFormatProvider culture)
        {
            if (value == null) return byte.MinValue;

            var parsedValue = byte.MinValue;
            byte.TryParse(value.ToString(), NumberStyles.Number, culture, out parsedValue);

            return parsedValue;
        }

        #endregion

        #endregion

        #region Validation Extensions
        
        /// <summary>
        /// Evaluates whether the current value is not equal to the value of comparison.
        /// </summary>
        /// <param name="source">The current value.</param>
        /// <param name="value">The comparison value.</param>
        public static bool NotEquals(this object source, object value)
        {
            if (source.IsNull()) return false;

            return !source.Equals(value);
        }

        /// <summary>
        /// Evaluates whether an object is a null value.
        /// </summary>
        /// <param name="value">The current value.</param>
        public static bool IsNull(this object value)
        {
            return (value == null);
        }

        /// <summary>
        /// Evaluates whether an object is not a null value.
        /// </summary>
        /// <param name="value">The current value.</param>
        public static bool IsNotNull(this object value)
        {
            return (value != null);
        }

        /// <summary>
        /// Checks if the object's value is not the type specified.
        /// </summary>
        /// <param name="value">The current value.</param>
        /// <typeparam name="T">The type of the comparison class.</typeparam>
        public static bool NotIs<T>(this object value)
        {
            return !(value is T);
        }

        /// <summary>
        /// Checks if the object's type is equals the System.Type specified.
        /// </summary>
        /// <param name="value">The current value.</param>
        /// <param name="type">The comparison System.Type.</param>
        public static bool IsTypeOf(this object value, Type type)
        {
            return value.GetType() == type;
        }

        /// <summary>
        /// Checks if the object's type is not equals the System.Type specified.
        /// </summary>
        /// <param name="value">The current value.</param>
        /// <param name="type">The comparison System.Type.</param>
        public static bool NotIsTypeOf(this object value, Type type)
        {
            return value.GetType() != type;
        }

        /// <summary>
        /// Evaluates whether the object is a string containing only numbers.
        /// </summary>
        /// <param name="value">The current value.</param>
        public static bool IsNumeric(this object value)
        {
            if (value.IsNull()) return false;

            return value.ToString().IsNumeric();
        }

        /// <summary>
        /// Evaluates whether the object is a string that does not contain only numbers.
        /// </summary>
        /// <param name="value">The current value.</param>
        public static bool NotIsNumeric(this object value)
        {
            if (value.IsNull()) return false;

            return value.ToString().IsNotNumeric();
        }

        /// <summary>
        /// Evaluates whether the current value is greater than or equal the comparison value.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The current value.</param>
        /// <param name="compareValue">The comparison value.</param>
        public static bool IsGreaterThan(this object value, object compareValue)
        {
            if (value.IsTypeOf(typeof(Int32)))
            {
                return value.ToInt32().IsGreaterThan(compareValue.ToInt32());
            }
            else if (value.IsTypeOf(typeof(Int64)))
            {
                return value.ToInt64().IsGreaterThan(compareValue.ToInt64());
            }
            else if (value.IsTypeOf(typeof(decimal)))
            {
                return value.ToDecimal().IsGreaterThan(compareValue.ToDecimal());
            }
            else if (value.IsTypeOf(typeof(DateTime)))
            {
                return value.ToDateTime().IsGreaterThan(compareValue.ToDateTime());
            }
            else if (value.IsTypeOf(typeof(double)))
            {
                return value.ToDouble().IsGreaterThan(compareValue.ToDouble());
            }
            else if (value.IsTypeOf(typeof(Int16)))
            {
                return value.ToInt16().IsGreaterThan(compareValue.ToInt16());
            }
            else if (value.IsTypeOf(typeof(byte)))
            {
                return value.ToByte().IsGreaterThan(compareValue.ToByte());
            }
            else
            {
                throw new ArgumentOutOfRangeException("value", string.Format(LeanLob_Core_Messages.InvalidTypeForOperation, value.GetType().Name, "IsGreaterThan"));
            }
        }

        /// <summary>
        /// Evaluates whether the current value is greater than the comparison value.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The current value.</param>
        /// <param name="compareValue">The comparison value.</param>
        public static bool IsGreaterThanEqual(this object value, object compareValue)
        {
            if (value.IsTypeOf(typeof(Int32)))
            {
                return value.ToInt32().IsGreaterThanEqual(compareValue.ToInt32());
            }
            else if (value.IsTypeOf(typeof(Int64)))
            {
                return value.ToInt64().IsGreaterThanEqual(compareValue.ToInt64());
            }
            else if (value.IsTypeOf(typeof(decimal)))
            {
                return value.ToDecimal().IsGreaterThanEqual(compareValue.ToDecimal());
            }
            else if (value.IsTypeOf(typeof(DateTime)))
            {
                return value.ToDateTime().IsGreaterThanEqual(compareValue.ToDateTime());
            }
            else if (value.IsTypeOf(typeof(double)))
            {
                return value.ToDouble().IsGreaterThanEqual(compareValue.ToDouble());
            }
            else if (value.IsTypeOf(typeof(Int16)))
            {
                return value.ToInt16().IsGreaterThanEqual(compareValue.ToInt16());
            }
            else if (value.IsTypeOf(typeof(byte)))
            {
                return value.ToByte().IsGreaterThanEqual(compareValue.ToByte());
            }
            else
            {
                throw new ArgumentOutOfRangeException("value", string.Format(LeanLob_Core_Messages.InvalidTypeForOperation, value.GetType().Name, "IsGreaterThanEqual"));
            }
        }

        /// <summary>
        /// Evaluates whether the current value is less than or equal the comparison value.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The current value.</param>
        /// <param name="compareValue">The comparison value.</param>
        public static bool IsLessThanEqual(this object value, object compareValue)
        {
            if (value.IsTypeOf(typeof(Int32)))
            {
                return value.ToInt32().IsLessThanEqual(compareValue.ToInt32());
            }
            else if (value.IsTypeOf(typeof(Int64)))
            {
                return value.ToInt64().IsLessThanEqual(compareValue.ToInt64());
            }
            else if (value.IsTypeOf(typeof(decimal)))
            {
                return value.ToDecimal().IsLessThanEqual(compareValue.ToDecimal());
            }
            else if (value.IsTypeOf(typeof(DateTime)))
            {
                return value.ToDateTime().IsLessThanEqual(compareValue.ToDateTime());
            }
            else if (value.IsTypeOf(typeof(double)))
            {
                return value.ToDouble().IsLessThanEqual(compareValue.ToDouble());
            }
            else if (value.IsTypeOf(typeof(Int16)))
            {
                return value.ToInt16().IsLessThanEqual(compareValue.ToInt16());
            }
            else if (value.IsTypeOf(typeof(byte)))
            {
                return value.ToByte().IsLessThanEqual(compareValue.ToByte());
            }
            else
            {
                throw new ArgumentOutOfRangeException("value", string.Format(LeanLob_Core_Messages.InvalidTypeForOperation, value.GetType().Name, "IsLessThanEqual"));
            }
        }

        /// <summary>
        /// Evaluates whether the current value is less than the comparison value.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The current value.</param>
        /// <param name="compareValue">The comparison value.</param>
        public static bool IsLessThan(this object value, object compareValue)
        {
            if (value.IsTypeOf(typeof(Int32)))
            {
                return value.ToInt32().IsLessThan(compareValue.ToInt32());
            }
            else if (value.IsTypeOf(typeof(Int64)))
            {
                return value.ToInt64().IsLessThan(compareValue.ToInt64());
            }
            else if (value.IsTypeOf(typeof(decimal)))
            {
                return value.ToDecimal().IsLessThan(compareValue.ToDecimal());
            }
            else if (value.IsTypeOf(typeof(DateTime)))
            {
                return value.ToDateTime().IsLessThan(compareValue.ToDateTime());
            }
            else if (value.IsTypeOf(typeof(double)))
            {
                return value.ToDouble().IsLessThan(compareValue.ToDouble());
            }
            else if (value.IsTypeOf(typeof(Int16)))
            {
                return value.ToInt16().IsLessThan(compareValue.ToInt16());
            }
            else if (value.IsTypeOf(typeof(byte)))
            {
                return value.ToByte().IsLessThan(compareValue.ToByte());
            }
            else
            {
                throw new ArgumentOutOfRangeException("value", string.Format(LeanLob_Core_Messages.InvalidTypeForOperation, value.GetType().Name, "IsLessThan"));
            }
        }

        /// <summary>
        /// Evaluates whether the current value not is between two comparison values.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The current value.</param>
        /// <param name="minValue">The lower comparison value.</param>
        /// <param name="maxValue">The higher comparison value.</param>
        public static bool IsNotBetween(this object value, object minValue, object maxValue)
        {
            return !IsBetween(value, minValue, maxValue);
        }

        /// <summary>
        /// Evaluates whether the current value is between two comparison values.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The current value.</param>
        /// <param name="minValue">The lower comparison value.</param>
        /// <param name="maxValue">The higher comparison value.</param>
        public static bool IsBetween(this object value, object minValue, object maxValue)
        {
            if (value.IsTypeOf(typeof(Int32)))
            {
                return value.ToInt32().IsBetween(minValue.ToInt32(), maxValue.ToInt32());
            }
            else if (value.IsTypeOf(typeof(Int64)))
            {
                return value.ToInt64().IsBetween(minValue.ToInt64(), maxValue.ToInt64());
            }
            else if (value.IsTypeOf(typeof(decimal)))
            {
                return value.ToDecimal().IsBetween(minValue.ToDecimal(), maxValue.ToDecimal());
            }
            else if (value.IsTypeOf(typeof(DateTime)))
            {
                return value.ToDateTime().IsBetween(minValue.ToDateTime(), maxValue.ToDateTime());
            }
            else if (value.IsTypeOf(typeof(double)))
            {
                return value.ToDouble().IsBetween(minValue.ToDouble(), maxValue.ToDouble());
            }
            else if (value.IsTypeOf(typeof(Int16)))
            {
                return value.ToInt16().IsBetween(minValue.ToInt16(), maxValue.ToInt16());
            }
            else if (value.IsTypeOf(typeof(byte)))
            {
                return value.ToByte().IsBetween(minValue.ToByte(), maxValue.ToByte());
            }
            else
            {
                throw new ArgumentOutOfRangeException("value", string.Format(LeanLob_Core_Messages.InvalidTypeForOperation, value.GetType().Name, "IsLessThan"));
            }
        }

        #endregion
    }
}